package com.bac.rentmap.ad.flat.search;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import com.bac.rentmap.ad.flat.FlatAd;
import com.bac.rentmap.ad.flat.GeoLocation;
import com.bac.rentmap.ad.flat.LocalAddress;
import com.bac.rentmap.ad.flat.LocalAddressPrefix;
import com.bac.rentmap.distance.DistanceCalculator;
import com.bac.rentmap.distance.DistanceCheckPoint;
import com.bac.rentmap.distance.DistanceCheckPointProvider;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Text;

/**
 * Searches for {@link FlatAd} using datastore low level API.
 * 
 * @author Andrew Osipenko
 */
public class DatastoreFlatAdKeySearcher implements FlatAdKeySearcher {
	private static final Logger log = Logger
			.getLogger(DatastoreFlatAdKeySearcher.class.getName());
	private DistanceCalculator distanceCalculator;
	private DistanceCheckPointProvider distanceCheckPointProvider;

	public List<Long> search(SearchParameters searchParameters) {
		log.fine("Searching FlatAds by parameters " + searchParameters);
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		Query query = new Query("FlatAd").setKeysOnly();
		query.addFilter("city", Query.FilterOperator.EQUAL,
				searchParameters.getCity());
        addPricePerDayFilter(query, searchParameters.getPricePerDay());
		mayBeAddPriceFilter(query, searchParameters.getMinPrice(),
				searchParameters.getMaxPrice());
		mayBeAddRoomFilter(query, searchParameters.getRoom());
		mayBeAddSourceFilter(query, searchParameters.getSourceUrl());
		addSort(searchParameters.getLat(), searchParameters.getLon(), query);
		int pageSize = searchParameters.getPageSize();
		List<Entity> entities = datastore.prepare(query).asList(
				FetchOptions.Builder.withLimit(pageSize).offset(
						searchParameters.getPage() * pageSize));
		log.fine("Found " + entities.size() + " flat ads");

		//return entities;
		return transform(entities);
	}
	
	private List<Long> transform(List<Entity> entities){
		List<Long> res = new ArrayList<Long>(entities.size());
		for (Entity entity : entities) {
			res.add(entity.getKey().getId());
		}
		return res;
	}

	private List<FlatAd> transform1(List<Entity> entities) {
		List<FlatAd> res = new ArrayList<FlatAd>(entities.size());
		for (Entity entity : entities) {
			FlatAd flatAd = new FlatAd();
			flatAd.setSecureId((String) entity.getProperty("secureId"));
			String streetPrefix = (String) entity.getProperty("streetPrefix");
			Number building = (Number) entity.getProperty("building");
			flatAd.setLocalAddress(new LocalAddress(streetPrefix != null? LocalAddressPrefix
					.valueOfName(streetPrefix): null,
					(String) entity.getProperty("street"), building != null ? building.intValue() : null));
			flatAd.setId(entity.getKey().getId());
			flatAd.setLastPublishingDate((Date) entity.getProperty("lastPublishingDate"));
			Text description = (Text) entity.getProperty("description");
			flatAd.setDescription(description != null ? description.getValue(): null);
			flatAd.setAgent((Boolean) entity.getProperty("agent"));
			flatAd.setFlatImages((List<Key>) entity.getProperty("flatImages"));
			flatAd.setPhone((String) entity.getProperty("phone"));
			flatAd.setPrice(((Number)entity.getProperty("price")).intValue());
			flatAd.setRating((Long) entity.getProperty("rating"));
			flatAd.setRoom((String) entity.getProperty("room"));
			Number agentUserReviewCount = (Number) entity.getProperty("agentUserReviewCount");
			if(agentUserReviewCount != null)
				flatAd.setAgentUserReviewCount(agentUserReviewCount.intValue());
			Number badPhoneUserReviewCount = (Number) entity.getProperty("badPhoneUserReviewCount");
			
			if(badPhoneUserReviewCount != null)
				flatAd.setBadPhoneUserReviewCount(badPhoneUserReviewCount.intValue());
			
			Number expiredAdUserReviewCount = (Number) entity.getProperty("expiredAdUserReviewCount");
			if(expiredAdUserReviewCount != null)
				flatAd.setExpiredAdUserReviewCount(expiredAdUserReviewCount.intValue());
			
			Double lat = (Double) entity.getProperty("lat");
			if(lat != null)
				flatAd.setGeoLocation(new GeoLocation(lat, (Double) entity.getProperty("lon")));
			flatAd.setExternalUrl((String) entity.getProperty("externalUrl"));
			res.add(flatAd);
		}
		return res;
	}

	private void mayBeAddRoomFilter(Query query, String room) {
		if (room != null) {
			query.addFilter("room", Query.FilterOperator.EQUAL, room);
		}
	}

	private void mayBeAddPriceFilter(Query query, Integer minPrice,
			Integer maxPrice) {
		if (minPrice != null || maxPrice != null) {
			query.addFilter("priceRanges", Query.FilterOperator.EQUAL,
					generatePriceRange(minPrice, maxPrice));
		}
	}

	private String generatePriceRange(Integer minPrice, Integer maxPrice) {
		return getPriceString(minPrice) + FlatAd.PRICE_RANGE_SEPARATOR
				+ getPriceString(maxPrice);
	}

	private String getPriceString(Integer price) {
		if (price != null)
			return price.toString();
		else
			return "";
	}

    private void addPricePerDayFilter(Query query, Boolean pricePerDay){
        query.addFilter("pricePerDay", Query.FilterOperator.EQUAL, pricePerDay);
    }
	
	private void mayBeAddSourceFilter(Query query, String sourceUrl){
	    if(sourceUrl != null){
            query.addFilter("sourceUrl", Query.FilterOperator.EQUAL, sourceUrl);
	    }
	}

	private void addSort(Double lat, Double lon, Query query) {
		if (lat != null) {
			List<DistanceCheckPoint> distanceCheckPoints = distanceCheckPointProvider
					.retrieveDistanceCheckPoints(null);
			int i = distanceCalculator.findNearestDistanceCheckPointIndex(lat,
					lon, distanceCheckPoints);
			query.addSort(FlatAd.DISTANCE_PROPERTY_PREFIX + i,
					Query.SortDirection.DESCENDING);
		} else {
			query.addSort("rating", Query.SortDirection.DESCENDING);
		}
	}

	public void setDistanceCalculator(DistanceCalculator distanceCalculator) {
		this.distanceCalculator = distanceCalculator;
	}

	public void setDistanceCheckPointProvider(
			DistanceCheckPointProvider distanceCheckPointProvider) {
		this.distanceCheckPointProvider = distanceCheckPointProvider;
	}
}
